#include<android/log.h>
#include <jni.h>
#include <string>
#include "log.h"
#include "FFmpegWrapper.h"


extern "C" {
#include "libavutil/log.h"
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavcodec/avcodec.h"
#include "libavutil/timestamp.h"
}

#define INBUF_SIZE 4096
#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096

static void yuv_save(AVFrame *pFrame, char buf[1024]);

static int get_format_from_sample_fmt(const char **fmt,enum AVSampleFormat sample_fmt);

void decodeAudio(AVCodecContext *pContext, AVPacket *pPacket, AVFrame *pFrame, FILE *pFile);

static void decode(AVCodecContext *avCodecContext, AVFrame *avFrame, AVPacket *pkt, const char *filename){
    char buf[1024];
    int ret;

    ret = avcodec_send_packet(avCodecContext,pkt);
    if(ret<0){
        LOGD( "avcodec_send_packet error:%s", av_err2str(ret));
        return;
    }

    while (ret>=0){
       ret = avcodec_receive_frame(avCodecContext,avFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return;
        } else if (ret < 0) {
            LOGD("Error during decoding\n");
            return;
        }

        //为防止文件太多观察不便，每20个avFrame中抽取一个并保存为文件
        if (avCodecContext->frame_number % 20 == 0) {
            LOGD("saving avFrame %3d\n",avCodecContext->frame_number);

            /* the picture is allocated by the decoder. no need tofree it */
            //拼接文件名
            //C库函数:int snprintf(char *str, size_t size, const char *format, ...),将可变参数(...)按照format格式化成字符串，
            //并将字符串复制到str中，size为要写入的字符的最大数目，超过size会被截断。
            snprintf(buf, sizeof(buf), "%s-%d.yuv", filename, avCodecContext->frame_number);
            yuv_save(avFrame, buf);
        }
    }
}

static void yuv_save(AVFrame *avFrame, char *filename) {
    FILE *file;

    file = fopen(filename, "we");
    if (!file) {
        LOGD("Could not open out file\n");
        return;
    }

    //这段代码的原理参考《YUV与FFmpeg.md》
    int width = avFrame->width;
    int height = avFrame->height;
    for (int i = 0; i < height; i++)
        fwrite(avFrame->data[0] + i * avFrame->linesize[0], 1, width, file);
    for (int j = 0; j < height / 2; j++)
        fwrite(avFrame->data[1] + j * avFrame->linesize[1], 1, width / 2, file);
    for (int k = 0; k < height / 2; k++)
        fwrite(avFrame->data[2] + k * avFrame->linesize[2], 1, width / 2, file);

    fclose(file);
}


static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag) {
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           tag,
           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
           av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
           pkt->stream_index);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_helloffmpeg_system_FfmpegHelper_decodeVideo(JNIEnv *env, jobject thiz,
                                                             jstring file_path,
                                                             jstring dst_path) {
    const char *srcPath =   env->GetStringUTFChars(file_path, nullptr);
    const char *dstPath =   env->GetStringUTFChars(dst_path, nullptr);

    const AVCodec *avCodec = nullptr;
    AVCodecParserContext *avCodecParserContext = nullptr;
    AVCodecContext *avCodecContext = nullptr;
    FILE *file = nullptr;
    AVFrame *avFrame = nullptr;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = nullptr;
    size_t data_size;
    int ret;
    AVPacket *avPacket = nullptr;

    //流程 avCodec--> data -->packet -->frame ---->yuv

    //1.create avCodec
    avPacket = av_packet_alloc();

    if(!avPacket){
        goto end;
    }

    memset(inbuf+INBUF_SIZE,0,AV_INPUT_BUFFER_PADDING_SIZE);

    avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if(!avCodec){
        LOGD( "avcodec find decoder error");
        goto end;
    }

    avCodecParserContext = av_parser_init(avCodec->id);
    if(!avCodecParserContext){
        LOGD( "avCodecParserContext not found");
        goto end;
    }

    avCodecContext = avcodec_alloc_context3(avCodec);
    if(!avCodecContext){
        LOGD( "avCodecContext not found");
        goto end;
    }

    /* For some codecs, such as msmpeg4 and mpeg4, width and height
       MUST be initialized there because this information is not
       available in the bitstream. */

    //2.open file to packet
    if(avcodec_open2(avCodecContext,avCodec, nullptr)<0){
        LOGD( "Could not open avCodec");
        goto end;
    }

    file = fopen(srcPath,"rbe");
    if(!file){
        LOGD( "Could not open file");
        goto end;
    }

    avFrame = av_frame_alloc();
    if(!avFrame){
        LOGD( "Could not allocate video avFrame");
        goto end;
    }

    while (!feof(file)){
        data_size = fread(inbuf,1,INBUF_SIZE,file);
        if(!data_size) break;

        /* use the avCodecParserContext to split the data into frames */
        data = inbuf;
        while (data_size > 0){
            ret = av_parser_parse2(avCodecParserContext,avCodecContext,
                             &avPacket->data,&avPacket->size,data,data_size,
                             AV_NOPTS_VALUE,AV_NOPTS_VALUE,0);
            if(ret<0){
                LOGD( "Error while parsing \n");
                goto end;
            }
            //后移数组指针并更新data_size
            data += ret;
            data_size -= ret;
            //解码avPacket
            if (avPacket->size)
                decode(avCodecContext, avFrame, avPacket, dstPath);
        }
    }

    /* flush the decoder */
    //3.pkt to yuv
    decode(avCodecContext, avFrame, nullptr, dstPath);

    end:
    env->ReleaseStringUTFChars(file_path, srcPath);
    env->ReleaseStringUTFChars(dst_path, dstPath);
    if (file) fclose(file);
    if (avCodecParserContext) av_parser_close(avCodecParserContext);
    if (avCodecContext) avcodec_free_context(&avCodecContext);
    if (avFrame) av_frame_free(&avFrame);
    if (avPacket) av_packet_free(&avPacket);

    return 0;

    return  -1;
}


/**
 * 根据采样格式获取be le信息
 */
static int get_format_from_sample_fmt(const char **fmt,
                                      enum AVSampleFormat sample_fmt) {
    int i;
    *fmt = nullptr;
    //采样格式与格式字符串的对应关系
    struct sample_fmt_entry {
        enum AVSampleFormat sample_fmt;
        const char *fmt_be, *fmt_le;
    } sample_fmt_entries[] = {
            {AV_SAMPLE_FMT_U8,  "u8",    "u8"},
            {AV_SAMPLE_FMT_S16, "s16be", "s16le"},
            {AV_SAMPLE_FMT_S32, "s32be", "s32le"},
            {AV_SAMPLE_FMT_FLT, "f32be", "f32le"},
            {AV_SAMPLE_FMT_DBL, "f64be", "f64le"},
    };

    //遍历sample_fmt_entries数组
    for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
        struct sample_fmt_entry *entry = &sample_fmt_entries[i];
        if (sample_fmt == entry->sample_fmt) {
            *fmt = AV_NE(entry->fmt_be, entry->fmt_le);
            return 0;
        }
    }

    LOGD("sample format %s is not supported as output format\n",av_get_sample_fmt_name(sample_fmt));
    return -1;
}



extern "C"
JNIEXPORT jint JNICALL
Java_com_example_helloffmpeg_system_FfmpegHelper_decodeAudio(JNIEnv *env, jobject thiz,
                                                             jstring file_path,
                                                             jstring dst_file_path) {
    const char *filename = env->GetStringUTFChars(file_path, nullptr);
    const char *outfilename = env->GetStringUTFChars(dst_file_path, nullptr);

    const AVCodec *avCodec = nullptr;
    AVCodecContext *avCodecContext = nullptr;
    AVCodecParserContext *avCodecParserContext = nullptr;
    int len, ret;
    FILE *infile = nullptr, *outfile = nullptr;
    uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = nullptr;
    size_t data_size;
    AVPacket *pkt = nullptr;
    AVFrame *avFrame = nullptr;
    enum AVSampleFormat avSampleFormat;
    int n_channels = 0;
    const char *fmt = nullptr;

    pkt = av_packet_alloc();

    avCodec = avcodec_find_decoder(AV_CODEC_ID_AAC);
    if (!avCodec) {
        LOGD("Codec not found\n");
        goto end;
    }

    avCodecParserContext = av_parser_init(avCodec->id);
    if (!avCodecParserContext) {
        LOGD("avCodecParserContext not found\n");
        goto end;
    }

    avCodecContext = avcodec_alloc_context3(avCodec);
    if (!avCodecContext) {
        LOGD("Could not allocate avCodecContext\n");
        goto end;
    }

    if (avcodec_open2(avCodecContext, avCodec, nullptr) < 0) {
        LOGD("Could not open avCodec\n");
        goto end;
    }

    infile = fopen(filename, "rbe");
    if (!infile) {
        LOGD("Could not open in file\n");
        goto end;
    }

    outfile = fopen(outfilename, "wbe");
    if (!outfile) {
        LOGD("Could not open out file\n");
        goto end;
    }

    /* decode until eof */
    data = inbuf;
    data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, infile);

    while (data_size > 0) {
        //如果avFrame为空，则创建一个avFrame
        if (!avFrame) {
            if (!(avFrame = av_frame_alloc())) {
                LOGD("Could not allocate video avFrame\n");
                goto end;
            }
        }

        //从data中解析出avPacket数据
        ret = av_parser_parse2(avCodecParserContext, avCodecContext, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0) {
            LOGD("Error while parsing\n");
            goto end;
        }
        //后移数组指针并更新data_size
        data += ret;
        data_size -= ret;

        //解码avPacket,并存入文件
        if (pkt->size)
            decodeAudio(avCodecContext, pkt, avFrame, outfile);

        //剩余的数据不多了，将剩余数据移动到inbuf的前部
        //并从文件中再读取一次数据
        if (data_size < AUDIO_REFILL_THRESH) {
            memmove(inbuf, data, data_size);
            data = inbuf;
            len = fread(data + data_size, 1,
                        AUDIO_INBUF_SIZE - data_size, infile);
            if (len > 0)
                data_size += len;
        }
    }

    /* flush the decoder */
    pkt->data = nullptr;
    pkt->size = 0;
    decodeAudio(avCodecContext, pkt, avFrame, outfile);

    //打印输出文件的信息
    /* print output pcm infomations, because there have no metadata of pcm */
    avSampleFormat = avCodecContext->sample_fmt;
    //采样格式如果是planar的,则获得packed版的采样格式
    //因为在decode函数中，我们将数据存为文件时，采用的是packed的存法
    if (av_sample_fmt_is_planar(avSampleFormat)) {
        //获得packed版的采样格式
        avSampleFormat = av_get_packed_sample_fmt(avSampleFormat);
    }
    n_channels = avCodecContext->channels;
    if (get_format_from_sample_fmt(&fmt, avSampleFormat) < 0)
        goto end;
    LOGD("Play the output audio file with the command:\n"
                        "ffplay -f %s -ac %d -ar %d %s\n",
                        fmt, n_channels, avCodecContext->sample_rate,
                        outfilename);

    //释放资源
    end:
    env->ReleaseStringUTFChars(file_path, filename);
    env->ReleaseStringUTFChars(dst_file_path, outfilename);
    if (outfile) fclose(outfile);
    if (infile) fclose(infile);
    if (avCodecContext) avcodec_free_context(&avCodecContext);
    if (avCodecParserContext) av_parser_close(avCodecParserContext);
    if (avFrame) av_frame_free(&avFrame);
    if (pkt) av_packet_free(&pkt);

    return 0;
}

void decodeAudio(AVCodecContext *avCodecContext, AVPacket *pkt, AVFrame *avFrame,
                 FILE *outfile) {
    int i, ch;
    int ret, data_size;

    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(avCodecContext, pkt);
    if (ret < 0) {
        LOGD("Error sending a packet for decoding: %s\n",
                            av_err2str(ret));
        return;
    }

    /* read all the output frames (in general there may be any number of them */
    while (ret >= 0) {
        //解码出frame并存入avFrame参数
        ret = avcodec_receive_frame(avCodecContext, avFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            LOGD("Error during decoding\n");
            return;
        }

        //获取该采样格式每个采样是多少字节
        //一个采样中可能包含多个声道，每个声道的数据大小都是data_size
        data_size = av_get_bytes_per_sample(avCodecContext->sample_fmt);
        if (data_size < 0) {
            /* This should not occur, checking just for paranoia */
            LOGD("Failed to calculate data size\n");
            return;
        }

        //遍历avFrame中的每一个采样数据
        for (i = 0; i < avFrame->nb_samples; i++)
            //遍历每一个声道
            for (ch = 0; ch < avCodecContext->channels; ch++)
                //文件中数据的排列格式：采样1声道1 采样1声道2 采样2声道1 采样2声道2...
                fwrite(avFrame->data[ch] + data_size * i, 1, data_size, outfile);
    }
}